package com.qfmy.app.service.impl.user;

import com.alibaba.cloud.nacos.utils.StringUtils;
import com.qfmy.app.config.ThreadPoolConfig;
import com.qfmy.app.mapper.user.UserMapper;
import com.qfmy.app.service.user.SsmService;
import com.qfmy.app.service.user.UserService;
import com.qfmy.app.vo.user.UserVo;
import com.qfmy.common.constant.RedisConstant;
import com.qfmy.common.exception.BaseException;
import com.qfmy.common.login.LoginUser;
import com.qfmy.common.login.LoginUserHolder;
import com.qfmy.common.result.ResultCodeEnum;
import com.qfmy.common.utils.CodeUtils;
import com.qfmy.common.utils.JwtUtil;
import com.qfmy.common.utils.PasswordUtil;
import com.qfmy.model.entity.query.LoginQuery;
import com.qfmy.model.entity.user.Users;
import com.qfmy.model.entity.vo.CaptchaVo;
import com.qfmy.model.enums.user.GenderStatus;
import com.qfmy.model.enums.user.UserStatus;
import com.qfmy.model.enums.user.UserType;
import com.wf.captcha.SpecCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author 清风明月
 * @Package com.qfmy.app.service.impl.user
 * @date 2025/4/13
 * @description 用户service
 */
@Slf4j
@Service
@SuppressWarnings("all")
public class UserServiceImpl implements UserService {

    /**
     * 注入redisTemplate
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 注入RedissonClient
     */
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 注入短信验证服务
     */
    @Autowired
    private SsmService ssmService;
    /**
     * 注入mapper
     */
    @Autowired
    private UserMapper userMapper;

    /**
     * 默认头像
     */
    @Value("${default.avatar}")
    private String defaultAvatarUrl;


    /**
     * 获取图形验证码
     * @return
     */
    @Override
    public CaptchaVo getCode() {
        // 创建分布式锁
        RLock lock = redissonClient.getLock("captchaGeneratorLock");
        //尝试获取锁
        try {
            boolean isTrue = lock.tryLock(30, 60, TimeUnit.SECONDS);
            //判断是否成功
            if(isTrue){
                //创建一个验证码
                SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
                String code = specCaptcha.text().toLowerCase();
                String key = RedisConstant.APP_LOGIN_PREFIX + UUID.randomUUID();
                //存入redis
                redisTemplate.opsForValue().set(key,code,RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC , TimeUnit.SECONDS);
                return new CaptchaVo(specCaptcha.toBase64(),key);
            }else {
                throw new BaseException(ResultCodeEnum.SERVICE_ERROR)      ;
            }
        } catch (InterruptedException e) {
           log.error("获取图形验证码失败",e);
           throw new BaseException(ResultCodeEnum.SERVICE_ERROR);
        }finally {
            //释放锁
            if(lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }

    /**
     * 获取短信验证码
     * @param phone
     */
    @Override
    public void getSmsCode(String phone) {
        // 获取redis里面的存放验证码的key
        String key = RedisConstant.APP_LOGIN_PREFIX + phone;
        // 获取验证码
        String code = (String) redisTemplate.opsForValue().get(key);

        // 如果验证码不存在，则通过Lua脚本原子生成和存储验证码
        if (StringUtils.isEmpty(code)) {
            // 定义Lua脚本
            String luaScript = "if redis.call('get', KEYS[1]) == false then " +
                    "local code = ARGV[1] " +
                    "redis.call('set', KEYS[1], code) " +
                    "redis.call('expire', KEYS[1], ARGV[2]) " +
                    "return code " +
                    "else " +
                    "return redis.call('get', KEYS[1]) " +
                    "end";

            // 将Lua脚本注册到Redis
            DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(luaScript);
            redisScript.setResultType(String.class);

            // 生成验证码
            code = CodeUtils.getCode(6);

            // 将整数转换为字符串
            String expireTime = String.valueOf(RedisConstant.APP_LOGIN_CODE_RESEND_TIME_SEC);

            // 执行Lua脚本
            code = redisTemplate.execute(redisScript, Collections.singletonList(key), code, expireTime);

            // 如果生成验证码成功，则发送短信
            if (redisTemplate.opsForValue().get(key) != null) {
                ssmService.sendSmsCode(phone, code);
            }
        }
    }

    /**
     * 登入
     * @param phone
     * @param code
     * @return
     */
    @Override
    public Map<String, String> login(String phone, String code) {
        // 1. 验证用户是否存在
        Users user = validateUserExists(phone);
//        // 2. 验证验证码
//        CompletableFuture.runAsync(() -> validateUserStatus(user), ThreadPoolConfig.executorService);
        // 3. 验证用户状态
       CompletableFuture.runAsync(() -> validateUserStatus(user), ThreadPoolConfig.executorService);
        // 4. 生成 token 和 refreshToken
        return generateTokens(user);
    }

    /**
     * 校验验证码
     *
     * @param phone
     * @return
     */
    @NotNull
    private Users validateUserExists(String phone) {
        Users user = userMapper.getUserByPhone(phone);
        if (user == null) {
            throw new BaseException(ResultCodeEnum.APP_LOGIN_IS_NOT_REGISTERED);
        }
        return user;
    }

    /**
     * 校验验证码
     * @param phone
     * @param code
     */
    private void validateCode(String phone, String code) {
        String redisCode = (String) redisTemplate.opsForValue().get(RedisConstant.APP_LOGIN_PREFIX + phone);
        if (redisCode == null || !redisCode.equalsIgnoreCase(code)) {
            throw new BaseException(ResultCodeEnum.APP_LOGIN_CODE_ERROR);
        }
    }

    /**
     * 校验用户状态
     * @param phone
     */
    private void validateUserStatus(Users user) {
        if (!UserStatus.NORMAL.getCode().equals(user.getStatus())) {
            throw new BaseException(ResultCodeEnum.APP_ACCOUNT_DISABLED_ERROR);
        }
    }

    /**
     * 生成token
     * @param phone
     * @return
     */
    @NotNull
    private Map<String, String> generateTokens(@NotNull Users user) {
        //获取手机号
        String phone = user.getPhone();
        String token = JwtUtil.generateAccessToken(phone, user.getUsername(), Long.valueOf(user.getUserId()));
        String refreshToken = JwtUtil.generateRefreshToken(phone, user.getUsername(), Long.valueOf(user.getUserId()));

        // 存储 token 和 refreshToken 到 Redis
        redisTemplate.opsForValue().set(RedisConstant.APP_TOKEN_PREFIX + phone, token, JwtUtil.accessTokenExpiration, TimeUnit.MILLISECONDS);
        redisTemplate.opsForValue().set(RedisConstant.APP_REFRESH_TOKEN_PREFIX + phone, refreshToken, JwtUtil.refreshTokenExpiration, TimeUnit.MILLISECONDS);
        // 异步存储 refreshToken 到数据库
        CompletableFuture.runAsync(() -> userMapper.updateRefreshToken(user.getUserId(), refreshToken), ThreadPoolConfig.executorService);

        Map<String, String> map = new HashMap<>();
        map.put("token", token);
        map.put("refreshToken", refreshToken);
        return map;
    }



    /**
     * 进行注册
     * @param user
     */
    @Override
    public void register(@NotNull LoginQuery user) {
        //根据手机号查询用户
        Users users = userMapper.getUserByPhone(user.getPhone());
        if(users != null)
        {
            throw new BaseException(ResultCodeEnum.APP_LOGIN_IS_REGISTERED);
        }
        //校验短信验证码
        validateCode(user.getPhone(), user.getCode());
        //校验图形验证码
        validateCaptcha(user.getImageCode(), user.getKey());
        //注册用户
        registerUser(user);
    }

    /**
     * 更新密码
     * @param user
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePassword(@NotNull LoginQuery user) {
        //校验用户是否存在
        Users users = validateUserExists(user.getPhone());
        //校验短信验证码
        validateCode(user.getPhone(), user.getCode());
        //获取密码
        String password = user.getPassword();
        //校验密码复杂度
        validatePasswordComplexity(password);
        //对密码进行加密
        String salt = PasswordUtil.encodePassword(password);
        //更新密码
        userMapper.updatePassword(users.getUserId(), salt);
    }

    /**
     * 刷新token
     * @param refreshToken
     * @return
     */
    @Override
    public Map<String, String> refreshToken(String refreshToken) {
        //校验refreshToken
        validateRefreshToken(refreshToken);
        //刷新token
        String token = JwtUtil.refreshToken(refreshToken);
        //返回token
        Map<String, String> map = new HashMap<>();
        map.put("token", token);
        map.put("refreshToken", refreshToken);
        return map;
    }

    /**
     * 获取用户信息
     * @return
     */
    @Override
    @Transactional(readOnly = true)
    public UserVo getInfo() {
        Users users = getUser();
        //进行对象拷贝
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(users, userVo);
        //获取用户的类型
        String type = UserType.getDescByCode(users.getRole());
        //设置用户类型
        userVo.setRole(type);
        //获取用户的性别
       String sex = GenderStatus.getDesc(users.getGender());
        //设置用户的性别
        userVo.setGender(sex);
        return userVo;
    }

    /**
     * 退出登录
     * @param userId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void logout() {
        //获取用户信息
        Users users = getUser();
        //获取手机号
        String phone = users.getPhone();
        //从redis里面删除token和refreshToken
        redisTemplate.delete(RedisConstant.APP_TOKEN_PREFIX + phone);
        redisTemplate.delete(RedisConstant.APP_REFRESH_TOKEN_PREFIX + phone);
        //从数据库里面删除refreshToken
        userMapper.updateRefreshToken(users.getUserId(), null);
    }

    /**
     * 更新用户信息
     * @param userVo
     */
    @Override
    public void updateInfo(UserVo userVo) {
        //获取用户信息
        Users users = getUser();
        //进行对象拷贝
        BeanUtils.copyProperties(userVo, users);
        //更新用户信息
        userMapper.updateUser(users);
    }

    /**
     * 更新用户密码
     * @param oldPassword
     * @param password
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void UpdatePassword(String oldPassword, String password) {
        //获取用户信息
        Users users = getUser();
        //获取密码
        String ps = users.getPassword();
        Boolean isTrue = PasswordUtil.matches(oldPassword, ps);
        //校验密码
        if(!isTrue)
        {
            throw new BaseException(ResultCodeEnum.APP_LOGIN_PASSWORD_NOT_TRUE);
        }
        //校验密码复杂度
        validatePasswordComplexity(password);
        //对密码进行加密
        String salt = PasswordUtil.encodePassword(password);
        //更新密码
        userMapper.UpdatePassword(Long.valueOf(users.getUserId()), salt);
    }

    /**
     * 获取用户信息
     */
    private Users getUser()
    {
        //从当前线程获取用户id
        LoginUser loginUser = LoginUserHolder.getLoginUser();
        if(loginUser == null)
        {
            throw new BaseException(ResultCodeEnum.TOKEN_INVALID);
        }
        //获取用户手机号
        String phone = loginUser.getPhone();
        //先从缓存里面获取用户信息
        String key =  phone;
        Users user = (Users) redisTemplate.opsForValue().get(key);
        if(user != null)
        {
            return user;
        }
        //根据手机号查询用户
        Users users = userMapper.getUserByPhone(phone);
        //将用户信息存入redis里面
        redisTemplate.opsForValue().set(key, String.valueOf(users),RedisConstant.APP_USER_TIME , TimeUnit.HOURS);
        return users;
    }

    /**
     * 校验refreshToken
     * @param refreshToken
     */
    @Transactional(readOnly = true)
    private void validateRefreshToken(String refreshToken) {
        //判断refreshToken是否存在
       String phone  = userMapper.IsRefreshToken(refreshToken);
       if(phone == null)
       {
           throw new BaseException(ResultCodeEnum.TOKEN_INVALID);
       }
       //从redis里面获取refreshToken
        String redisRefreshToken = (String) redisTemplate.opsForValue().get(RedisConstant.APP_REFRESH_TOKEN_PREFIX + phone);
       //判断是否为空
        if(redisRefreshToken == null)
        {
            throw new BaseException(ResultCodeEnum.TOKEN_EXPIRED);
        }
    }

    /**
     * 校验密码复杂度
     * @param password
     */
    private void validatePasswordComplexity(@NotNull String password) {
        if (password.length() < 8 || !password.matches(".*[A-Z].*") || !password.matches(".*[a-z].*") || !password.matches(".*\\d.*")) {
            log.error("密码不符合复杂度要求，密码：{}", password);
            throw new BaseException(ResultCodeEnum.APP_PASSWORD_COMPLEXITY_ERROR);
        }
    }

    /**
     * 注册用户
     * @param user
     */
    @Transactional(rollbackFor = Exception.class)
    private void registerUser(@NotNull LoginQuery user) {
        //获取密码
        String password = user.getPassword();
        //对密码进行加密
        String salt = PasswordUtil.encodePassword(password);
        //设置用户信息
        Users users = new Users();
        users.setPhone(user.getPhone());
        //创建用户名称+ 手机号后4位
        String username = "用户_" + user.getPhone().substring(user.getPhone().length() - 4);
        users.setUsername(username);
        //设置密码
        users.setPassword(salt);
        //设置默认的头像
        users.setAvatar(defaultAvatarUrl);
        //更新用户
        userMapper.insertUser(users);
    }

    /**
     * 校验图形验证码
     * @param imageCode
     * @param key
     */
    private void validateCaptcha(String imageCode, String key) {
        //从redis里面获取验证码
        String redisCode = (String) redisTemplate.opsForValue().get(key);
        //判断验证码是否正确
        if(redisCode == null || !redisCode.equalsIgnoreCase(imageCode))
        {
            throw new BaseException(ResultCodeEnum.APP_LOGIN_IMAGE_CODE_ERROR);
        }
    }

}
